﻿#include "proc_stat_impl.hh"
#include "../../thirdparty/jsoncpp/include/json/json.h"
#include "../argument/box_argument.hh"
#include "../box/box_os.hh"
#include "../box/service_box.hh"
#include "../detail/http_base_impl.hh"
#include "../detail/lang_impl.hh"
#include "../util/os_util.hh"
#include "../util/string_util.hh"
#include "../util/time_util.hh"
#include <any>
#include <array>
#include <chrono>
#include <cstring>
#include <fstream>

#if !defined(WIN32) && !defined(WIN64)
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#else
#include <windows.h>
#include <psapi.h>
#endif // !defined(WIN32) && !defined(WIN64)

#define ADD_PROC_STAT_MODULE(name, type)                                       \
  static auto add_proc_module_##type = kratos::service::module_map_.emplace(   \
      std::string(name), new kratos::service::type());

namespace kratos {
namespace service {

using ModuleMap =
    std::unordered_map<std::string, std::unique_ptr<ProcStatModule>>;
ModuleMap module_map_; ///< {模块名，模块}

using FastValueArray =
    std::array<std::any, static_cast<std::underlying_type<ValueType>::type>(
                             ValueType::MAX_SLOT)>;
FastValueArray value_array_; ///< {属性类型，属性值}

#if !defined(WIN32) && !defined(WIN64)
class CpuUsageModule : public ProcStatModule {
  struct rusage rusage1_;
  struct rusage rusage2_;
  std::time_t last_tick_{0};
  ProcStatImpl *proc_impl_{nullptr};

public:
  virtual ~CpuUsageModule() {}
  virtual auto start(ProcStatImpl *process) -> bool override {
    proc_impl_ = process;
    return true;
  }
  virtual auto stop() -> void override {}
  virtual auto update(std::time_t tick) -> void override {
    if (0 == last_tick_) {
      last_tick_ = tick;
      getrusage(RUSAGE_SELF, &rusage1_);
    } else {
      if (tick - last_tick_ > 1000) {
        getrusage(RUSAGE_SELF, &rusage2_);
        last_tick_ = tick;
        update_usage();
        memcpy(&rusage1_, &rusage2_, sizeof(rusage1_));
      }
    }
  }
  auto update_usage() -> void {
    auto total_start =
        rusage1_.ru_utime.tv_sec * 1000000 + rusage1_.ru_utime.tv_usec;
    auto total_end =
        rusage2_.ru_utime.tv_sec * 1000000 + rusage2_.ru_utime.tv_usec;
    std::uint64_t percent = (std::uint64_t)(
        ((double)(total_end - total_start) / 1000000.0f) * 100.0f);
    percent *= (std::uint64_t)sysconf(_SC_NPROCESSORS_ONLN);
    proc_impl_->update_value("cpu_usage", std::to_string(percent));
    proc_impl_->update_array_value(ValueType::CPU_USAGE, percent);
  }
};

class MemoryModule : public ProcStatModule {
  struct rusage rusage_;
  std::time_t last_tick_{0};
  ProcStatImpl *proc_impl_{nullptr};

public:
  virtual ~MemoryModule() {}
  virtual auto start(ProcStatImpl *process) -> bool override {
    proc_impl_ = process;
    return true;
  }
  virtual auto stop() -> void override {}
  virtual auto update(std::time_t tick) -> void override {
    if (0 == last_tick_) {
      last_tick_ = tick;
    } else {
      if (tick - last_tick_ > 1000) {
        getrusage(RUSAGE_SELF, &rusage_);
        last_tick_ = tick;
        update_usage();
      }
    }
  }
  auto update_usage() -> void {
    proc_impl_->update_value("mem_occupy(K)",
                             std::to_string(rusage_.ru_maxrss) + "K");
    proc_impl_->update_value(
        "mem_occupy(M)",
        std::to_string((std::uint64_t)((double)rusage_.ru_maxrss / 1000.0f)) +
            "M");
    proc_impl_->update_array_value(ValueType::MEM_OCCUPY_K,
                                   std::to_string(rusage_.ru_maxrss) + "K");
    proc_impl_->update_array_value(
        ValueType::MEM_OCCUPY_M,
        std::to_string((double)rusage_.ru_maxrss / 1000.0f) + "M");
  }
};
#else
class CpuUsageModule : public ProcStatModule {
  SYSTEMTIME kernal_time1_;
  SYSTEMTIME usr_time1_;
  SYSTEMTIME kernal_time2_;
  SYSTEMTIME usr_time2_;
  std::time_t last_tick_{0};
  HANDLE cur_proc_handle_{INVALID_HANDLE_VALUE};
  ProcStatImpl *proc_impl_{nullptr};

public:
  virtual ~CpuUsageModule() {}
  virtual auto start(ProcStatImpl *process) -> bool override {
    proc_impl_ = process;
    cur_proc_handle_ = ::GetCurrentProcess();
    return true;
  }
  virtual auto stop() -> void override {}
  virtual auto update(std::time_t tick) -> void override {
    if (0 == last_tick_) {
      last_tick_ = tick;
      getrusage(&kernal_time1_, &usr_time1_);
    } else {
      if (tick - last_tick_ > 1000) {
        getrusage(&kernal_time2_, &usr_time2_);
        last_tick_ = tick;
        update_usage();
        memcpy(&kernal_time1_, &kernal_time2_, sizeof(kernal_time2_));
        memcpy(&usr_time1_, &usr_time2_, sizeof(usr_time2_));
      }
    }
  }
  void getrusage(SYSTEMTIME *ktime, SYSTEMTIME *utime) {
    FILETIME ftCreation, ftExit, ftKernel, ftUser;
    if (FALSE == ::GetProcessTimes(cur_proc_handle_, &ftCreation, &ftExit,
      &ftKernel, &ftUser)) {
      return;
    }
    FileTimeToSystemTime(&ftKernel, ktime);
    FileTimeToSystemTime(&ftUser, utime);
  }
  auto update_usage() -> void {
    constexpr static std::uint64_t DAY_MICRO = 86400000000;
    constexpr static std::uint64_t HOUR_MICRO = 3600000000;
    constexpr static std::uint64_t MINUTE_MICRO = 60000000;
    constexpr static std::uint64_t SECOND_MICRO = 1000000;
    auto total_start = (std::uint64_t)kernal_time1_.wDay * DAY_MICRO +
                       (std::uint64_t)kernal_time1_.wHour * HOUR_MICRO +
                       (std::uint64_t)kernal_time1_.wMinute * MINUTE_MICRO +
                       (std::uint64_t)kernal_time1_.wSecond * SECOND_MICRO +
                       (std::uint64_t)kernal_time1_.wMilliseconds * 1000;
    auto total_end = (std::uint64_t)kernal_time2_.wDay * DAY_MICRO +
                     (std::uint64_t)kernal_time2_.wHour * HOUR_MICRO +
                     (std::uint64_t)kernal_time2_.wMinute * MINUTE_MICRO +
                     (std::uint64_t)kernal_time2_.wSecond * SECOND_MICRO +
                     (std::uint64_t)kernal_time2_.wMilliseconds * 1000;
    auto percent = ((double)total_end - (double)total_start) / 1000000.0f;
    proc_impl_->update_value(
      "cpu_usage", std::to_string((std::uint64_t)(percent * 100.0f)) + "%");
    proc_impl_->update_array_value(ValueType::CPU_USAGE,
      (std::uint64_t)percent);
  }
};

class MemoryModule : public ProcStatModule {
  PROCESS_MEMORY_COUNTERS counter_;
  std::time_t last_tick_{0};
  HANDLE cur_proc_handle_{INVALID_HANDLE_VALUE};
  ProcStatImpl *proc_impl_{nullptr};

public:
  virtual ~MemoryModule() {}
  virtual auto start(ProcStatImpl *process) -> bool override {
    proc_impl_ = process;
    cur_proc_handle_ = ::GetCurrentProcess();
    return true;
  }
  virtual auto stop() -> void override {}
  virtual auto update(std::time_t tick) -> void override {
    if (0 == last_tick_) {
      last_tick_ = tick;
      ::GetProcessMemoryInfo(cur_proc_handle_, &counter_, sizeof(counter_));
      update_usage();
    } else {
      if (tick - last_tick_ > 1000) {
        ::GetProcessMemoryInfo(cur_proc_handle_, &counter_, sizeof(counter_));
        last_tick_ = tick;
        update_usage();
      }
    }
  }
  auto update_usage() -> void {
    proc_impl_->update_value(
      "mem_occupy(K)", std::to_string(counter_.WorkingSetSize / 1000) + "K");
    proc_impl_->update_value(
      "mem_occupy(M)", std::to_string((std::uint64_t)(
      (double)counter_.WorkingSetSize / 1000000.0f)) +
      "M");
    proc_impl_->update_array_value(
      ValueType::MEM_OCCUPY_K,
      std::to_string(counter_.WorkingSetSize / 1000) + "K");
    proc_impl_->update_array_value(
      ValueType::MEM_OCCUPY_M,
      std::to_string(
      (std::uint64_t)((double)counter_.WorkingSetSize / 1000000.0f)) +
      "M");
  }
};

#endif // !defined(WIN32) && !defined(WIN64)

class TimeModule : public ProcStatModule {
  ProcStatImpl *proc_impl_{nullptr};

public:
  virtual ~TimeModule() {}
  virtual auto start(ProcStatImpl *process) -> bool override {
    proc_impl_ = process;
    return true;
  }
  virtual auto stop() -> void override {}
  virtual auto update(std::time_t tick) -> void override {
    proc_impl_->update_value(
      "Low cost/frame",
      std::to_string(util::get_lowest_cost_millionsecond()) + " MS");
    proc_impl_->update_value(
      "Highest cost/frame",
      std::to_string(util::get_highest_cost_millionsecond()) + " MS");
    proc_impl_->update_value("Current frame",
      std::to_string(util::get_real_frame()) + "/S");
    proc_impl_->update_value(
      "Maximum frame in configuration",
      std::to_string(proc_impl_->get_box()->get_argument().get_max_frame()) +
      "/S");
    proc_impl_->update_value(
      "Current frame cost",
      std::to_string(util::get_current_cost_millionsecond()) + " MS");
  }
};

} // namespace service
} // namespace kratos

kratos::service::ProcStatImpl::ProcStatImpl() {}

kratos::service::ProcStatImpl::~ProcStatImpl() {
  if (running_) {
    stop();
  }
}

auto kratos::service::ProcStatImpl::get_value(const std::string &name,
                                              std::string &value) -> bool {
  std::lock_guard<std::mutex> guard(value_map_mutex_);
  auto it = value_map_.find(name);
  if (it == value_map_.end()) {
    return false;
  }
  value = it->second;
  return true;
}

auto kratos::service::ProcStatImpl::get(ValueMap &value_map) -> bool {
  value_map = value_map_;
  return true;
}

inline auto index(kratos::service::ValueType type)
    -> std::underlying_type_t<kratos::service::ValueType> {
  return static_cast<std::underlying_type<kratos::service::ValueType>::type>(
      type);
}

void do_report(kratos::http::HttpBaseImpl *http, const std::string &host,
               int port, const std::string &uri) {
  Json::Value report;
  report["cpu_usage"] =
      std::any_cast<std::uint64_t>(kratos::service::value_array_[index(
          kratos::service::ValueType::CPU_USAGE)]);
  http->do_request_async(host, port, uri, "put", {}, report.toStyledString(),
                         500, 0, nullptr);
}

auto kratos::service::ProcStatImpl::start(ServiceBox *box) -> bool {
  box_ = box;
  for (auto &[k, v] : module_map_) {
    v->start(this);
  }
  // 不检查返回值，内部有日志
  load_config();
  update_fixed_value();
  running_ = true;
  worker_ = std::thread([&]() {
    std::string report_url;
    std::string host;
    int port = 80;
    if (!report_api_.empty() && !report_host_.empty()) {
      std::vector<std::string> result;
      util::split(report_host_, ":", result);
      if (result.empty() || result.size() == 1) {
        host = report_host_;
      } else {
        try {
          port = std::stoi(result[1]);
        } catch (std::exception &e) {
          if (box_) {
            box_->write_log(
              lang::LangID::LANG_UNEXPECTED_EXCEPTION,
              klogger::Logger::FAILURE,
              "ProcStatImpl",
              util::demangle(typeid(e).name()).c_str(),
              e.what()
            );
          }
        }
      }
    }
    kratos::http::HttpBaseImpl http(box_);
    while (running_) {
      auto tick = util::get_os_time_millionsecond();
      for (auto &[_, v] : module_map_) {
        v->update(tick);
      }
      // 每一秒进行一次记录
      std::this_thread::sleep_for(std::chrono::seconds(1));
      if (!report_host_.empty()) {
        do_report(&http, host, port, report_api_);
      }
      http.update(util::get_os_time_millionsecond());
    }
    http.stop();
  });
  return false;
}

auto kratos::service::ProcStatImpl::stop() -> bool {
  running_ = false;
  if (worker_.joinable()) {
    worker_.join();
  }
  for (auto &[_, v] : module_map_) {
    v->stop();
  }
  module_map_.clear();
  return true;
}

auto kratos::service::ProcStatImpl::add_module(const std::string &name,
  ProcStatModule *module) -> bool {
  module_map_[name].reset(module);
  return true;
}

auto kratos::service::ProcStatImpl::update_value(const std::string &name,
  const std::string &value)
    -> void {
  std::lock_guard<std::mutex> guard(value_map_mutex_);
  value_map_[name] = value;
}

auto kratos::service::ProcStatImpl::update_array_value(
  kratos::service::ValueType type, std::any &&value) -> void {
  value_array_[static_cast<std::underlying_type<ValueType>::type>(type)] =
      value;
}

auto kratos::service::ProcStatImpl::get_box() -> ServiceBox * { return box_; }

auto kratos::service::ProcStatImpl::get_value_any_internal(ValueType type)
    -> const std::any & {
  static std::any NullAny;
  auto index = static_cast<std::underlying_type<ValueType>::type>(type);
  auto max_index =
      static_cast<std::underlying_type<ValueType>::type>(ValueType::MAX_SLOT);
  if (index >= max_index) {
    return NullAny;
  }
  return value_array_[index];
}

auto kratos::service::ProcStatImpl::load_config() -> bool {
  auto *config = box_->get_config().get_config_ptr();
  if (config->has("report.stat_api")) {
    auto *attr = config->get("report.stat_api");
    if (!attr) {
      box_->write_log(
        lang::LangID::LANG_CONFIG_MISSING_ATTR,
        klogger::Logger::WARNING,
        "report.stat_api"
      );
      return false;
    }
    if (!attr->isString()) {
      box_->write_log(
        lang::LangID::LANG_CONFIG_TYPE_ERROR,
        klogger::Logger::WARNING,
        "report.stat_api"
      );
      return false;
    }
    report_api_ = config->string("report.stat_api")->get();
  }
  if (config->has("report.stat_api_host")) {
    auto *attr = config->get("report.stat_api_host");
    if (!attr) {
      box_->write_log(
        lang::LangID::LANG_CONFIG_MISSING_ATTR,
        klogger::Logger::WARNING,
        "report.stat_api_host"
      );
      return false;
    }
    if (!attr->isString()) {
      box_->write_log(
        lang::LangID::LANG_CONFIG_TYPE_ERROR,
        klogger::Logger::WARNING,
        "report.stat_api_host"
      );
      return false;
    }
    report_host_ = config->string("report.stat_api_host")->get();
  }
  return true;
}

auto kratos::service::ProcStatImpl::update_fixed_value() -> void {
  update_value("PID file", get_pid_file_path(box_));
  update_array_value(ValueType::PID_FILE, get_pid_file_path(box_));
  update_value(
    "Daemon",
    box_->get_argument().is_daemon() || box_->get_config().is_start_as_daemon()
      ? "yes" : "no");
  update_array_value(
    ValueType::DAEMON, ///< 是否是daemon
    box_->get_argument().is_daemon() || box_->get_config().is_start_as_daemon()
      ? "yes" : "no");
  update_value("Config file path", box_->get_argument().get_config_file_path());
  update_array_value(
    ValueType::CONFIG_FILE_PATH,
    box_->get_argument().get_config_file_path());
#ifdef WIN32
  update_value("OS", "Windows");
#else
  update_value("OS", "Linux");
#endif // WIN32
  update_value("PID", std::to_string(util::get_pid()));
}

ADD_PROC_STAT_MODULE("cpu_usage", CpuUsageModule);
ADD_PROC_STAT_MODULE("mem_occupy", MemoryModule);
ADD_PROC_STAT_MODULE("time", TimeModule);
